package xyz.erupt.tpl.controller;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.web.bind.annotation.*;
import xyz.erupt.annotation.sub_erupt.RowOperation;
import xyz.erupt.annotation.sub_erupt.Tpl;
import xyz.erupt.core.annotation.EruptRouter;
import xyz.erupt.core.config.GsonFactory;
import xyz.erupt.core.exception.EruptNoLegalPowerException;
import xyz.erupt.core.invoke.DataProcessorManager;
import xyz.erupt.core.invoke.ExprInvoke;
import xyz.erupt.core.service.EruptCoreService;
import xyz.erupt.core.util.EruptSpringUtil;
import xyz.erupt.core.util.EruptUtil;
import xyz.erupt.core.util.Erupts;
import xyz.erupt.core.view.EruptModel;
import xyz.erupt.tpl.annotation.EruptTpl;
import xyz.erupt.tpl.annotation.TplAction;
import xyz.erupt.tpl.engine.EngineConst;
import xyz.erupt.tpl.service.EruptTplService;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static xyz.erupt.core.constant.EruptRestPath.ERUPT_API;

/**
 * Erupt 页面结构构建信息
 *
 * @author YuePeng
 * date 2018-09-28.
 */
@RestController
@RequestMapping(ERUPT_API + EruptTplController.TPL)
@Slf4j
public class EruptTplController implements EruptRouter.VerifyHandler {

    static final String TPL = "/tpl";
    // 根据spring获取方法参数名称方式，获取方法参数名  Ajian
    private static final LocalVariableTableParameterNameDiscoverer ParameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
    // 缓存方法签名及对应的方法参数信息  Ajian
    private static final Map<String, String[]> CacheMethodParameterNamesMap = new HashMap<>();

    @Resource
    private EruptTplService eruptTplService;

    @GetMapping(value = "/**", produces = {"text/html;charset=utf-8"})
    @EruptRouter(authIndex = 1, verifyType = EruptRouter.VerifyType.MENU, verifyHandler = EruptTplController.class,
            verifyMethod = EruptRouter.VerifyMethod.PARAM)
    public void eruptTplPage(HttpServletRequest request, HttpServletResponse response) throws Exception {
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        String path = request.getRequestURI().split(ERUPT_API + EruptTplController.TPL + "/")[1];
        Method method = eruptTplService.getAction(path);
        if (null == method) {
            eruptTplService.tplRender(Tpl.Engine.Native, TPL + "/" + path, null, response.getWriter());
            return;
        }
        Object obj = EruptSpringUtil.getBean(method.getDeclaringClass());
        EruptTpl eruptTpl = obj.getClass().getAnnotation(EruptTpl.class);
        TplAction tplAction = method.getAnnotation(TplAction.class);
        path = TPL + "/" + path;
        if (StringUtils.isNotBlank(tplAction.path())) {
            path = tplAction.path();
        }
        //新增判断自定义的handler方法是否有基本类型或request类型入参，如有，则传入 Ajian
        Type[] argTypes = method.getParameterTypes();
        Object methodResult = null;
        if (argTypes.length > 0) {
            String cacheKey = method.getDeclaringClass().toGenericString() + "#" + method.toGenericString();
            String[] argNames = CacheMethodParameterNamesMap.get(cacheKey);
            try {
                if (argNames == null || argNames.length <= 0) {
                    argNames = ParameterNameDiscoverer.getParameterNames(method);
                    CacheMethodParameterNamesMap.put(cacheKey, argNames);
                }
                if ((argNames == null ? 0 : argNames.length) != argTypes.length) {
                    throw new Exception("方法入参数据类型个数和入参参数名个数不对应，请检查！");
                }
                Object[] paramVals = new Object[argTypes.length];
                for (int i = 0; i < argTypes.length; i++) {
                    paramVals[i] = buildMethodArgs(request, (Class<?>) argTypes[i], argNames[i]);
                }
                methodResult = method.invoke(obj, paramVals);
            } catch (Exception e) {
                log.error("获取method[{}]入参或构建入参值失败！", method.getName(), e);
                methodResult = method.invoke(obj);
            }
        } else {
            methodResult = method.invoke(obj);
        }

        eruptTplService.tplRender(eruptTpl.engine(), path, (Map) methodResult, response.getWriter());
    }

    /**
     * 根据方法的入参类型和入参名称，尝试获取请求对象中的参数返回
     *
     * @param request
     * @param argType
     * @param argName
     * @return java.lang.Object
     * @author Ajian
     * @date 2022-08-20 22:56
     */
    private Object buildMethodArgs(HttpServletRequest request, Class<?> argType, String argName) {
        if (argType.isAssignableFrom(ServletRequest.class)) {
            return request;
        }
        if (argType.isAssignableFrom(String.class)) {
            return request.getParameter(argName);
        }
        String errorMsg = "尝试为TplHandler方法设置入参[{}]时失败！";
        if (argType.isAssignableFrom(Integer.class)) {
            try {
                return Integer.valueOf(request.getParameter(argName));
            } catch (Exception e) {
                log.error(errorMsg, argName, e);
                return null;
            }
        }
        if (argType.isAssignableFrom(Float.class)) {
            try {
                return Float.valueOf(request.getParameter(argName));
            } catch (Exception e) {
                log.error(errorMsg, argName, e);
                return null;
            }
        }
        if (argType.isAssignableFrom(Double.class)) {
            try {
                return Double.valueOf(request.getParameter(argName));
            } catch (Exception e) {
                log.error(errorMsg, argName, e);
                return null;
            }
        }
        if (argType.isAssignableFrom(Long.class)) {
            try {
                return Long.valueOf(request.getParameter(argName));
            } catch (Exception e) {
                log.error(errorMsg, argName, e);
                return null;
            }
        }
        if (argType.isAssignableFrom(BigDecimal.class)) {
            try {
                return new BigDecimal(request.getParameter(argName));
            } catch (Exception e) {
                log.error(errorMsg, argName, e);
                return null;
            }
        }
        if (argType.isAssignableFrom(Boolean.class)) {
            try {
                return Boolean.valueOf(request.getParameter(argName));
            } catch (Exception e) {
                log.error(errorMsg, argName, e);
                return null;
            }
        }
        if (argType.isAssignableFrom(Date.class)) {
            try {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String arg = URLDecoder.decode(request.getParameter(argName), "UTF-8");
                return sdf.parse(arg);
            } catch (Exception e) {
                log.error(errorMsg, argName, e);
                return null;
            }
        }
        return null;
    }

    @Override
    public String convertAuthStr(EruptRouter eruptRouter, HttpServletRequest request, String authStr) {
        return request.getRequestURI().split(ERUPT_API + EruptTplController.TPL + "/")[1];
    }

    @GetMapping(value = "/html-field/{erupt}/{field}", produces = {"text/html;charset=UTF-8"})
    @EruptRouter(authIndex = 2, verifyType = EruptRouter.VerifyType.MENU, verifyMethod = EruptRouter.VerifyMethod.PARAM)
    public void getEruptFieldHtml(@PathVariable("erupt") String eruptName, @PathVariable("field") String field, HttpServletResponse response) {
        EruptModel eruptModel = EruptCoreService.getErupt(eruptName);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        Tpl tpl = eruptModel.getEruptFieldMap().get(field).getEruptField().edit().tplType();
        eruptTplService.tplRender(tpl, null, response);
    }

    @GetMapping(value = "/operation_tpl/{erupt}/{code}", produces = {"text/html;charset=utf-8"})
    @EruptRouter(authIndex = 2, verifyType = EruptRouter.VerifyType.ERUPT, verifyMethod = EruptRouter.VerifyMethod.PARAM)
    public void getOperationTpl(@PathVariable("erupt") String eruptName, @PathVariable("code") String code,
                                @RequestParam(value = "ids", required = false) String[] ids, HttpServletResponse response) {
        EruptModel eruptModel = EruptCoreService.getErupt(eruptName);
        RowOperation operation = Arrays.stream(eruptModel.getErupt().rowOperation()).filter(it ->
                it.code().equals(code)).findFirst().orElseThrow(EruptNoLegalPowerException::new);
        Erupts.powerLegal(ExprInvoke.getExpr(operation.show()));
        if (operation.tpl().engine() == Tpl.Engine.Native || operation.mode() == RowOperation.Mode.BUTTON) {
            eruptTplService.tplRender(operation.tpl(), null, response);
            return;
        }
        Map<String, Object> map = new HashMap<>();
        List<Object> list = Stream.of(ids).map(id -> DataProcessorManager.getEruptDataProcessor(eruptModel.getClazz())
                .findDataById(eruptModel, EruptUtil.toEruptId(eruptModel, id))).collect(Collectors.toList());
        map.put(EngineConst.INJECT_ROWS, list);
        //将选中的行数据列表，转换为JSON格式输出，方便页面直接获取 Ajian 20221024
        map.put(EngineConst.INJECT_ROWS_JSON, GsonFactory.getGson().toJson(list));
        eruptTplService.tplRender(operation.tpl(), map, response);
    }
}
